﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace Red.GameData.Raw
{ 
    /// <summary>
    /// Class reflection for the game data system, another central class next to the DataObject
    /// </summary>  
    public class ReflectionClass : ReflectionType
    {
        /// <summary>
        /// Was this type fully defined (set true only after Loading occurs)
        /// We need this flag to detect unresolved types
        /// </summary>
        public bool IsDefined { get; private set; }

        /// <summary>
        /// Is this class considered "abstract" ? If so - no objects of this class are allowed to exist directly
        /// This flag is useful to add "technical" classes to the class hierarchy that we don't want to instantiate directly
        /// </summary>
        public bool IsAbstract { get; private set; }

        /// <summary>
        /// Name of the property considered to be the "key" property for this class
        /// This is the basis of differentiating between named and unnamed objects.
        /// "Name" of the object is considered to be the value of the "key" property.
        /// Obviously, only objects that are created from class that has a Key property can be listed by that name.
        /// NOTE: this property is usually set for the base classes only, so in order to get the proper value of the KeyName property in derived classes
        /// you need to walk up the hierarchy. Alternatively, you can use the KeyName attribute that will do that for you.
        /// </summary>
        public string ClassKeyName { get; private set; }

        /// <summary>
        /// Name of the property considered to be the "key" property for this class
        /// This is the basis of differentiating between named and unnamed objects.
        /// FLATTENED (base classes are taken into account)
        /// </summary>
        public string KeyName
        {
            get
            {
                var ptr = this;
                while (ptr != null)
                {
                    if (ptr.ClassKeyName != "")
                        return ptr.ClassKeyName;

                    ptr = ptr.Parent;
                }

                // no key property
                return "";
            }
        }
        

        /// <summary>
        /// Is this a named class (with a key property defined?)
        /// Named classes allowed indexation of objects created from them
        /// </summary>
        public bool IsNamed
        {
            get
            {
                return (KeyName != "");
            }
        }

        /// <summary>
        /// Get the class from this class tree that has the actual key property defined
        /// </summary>
        /// <returns></returns>
        public ReflectionClass RootKeyClass
        {
            get
            {
                if (ClassKeyName != "")
                    return this;

                if (Parent != null)
                    return Parent.RootKeyClass;

                return null;
            }
        }


        /// <summary>
        /// Parent (base) class 
        /// </summary>
        public ReflectionClass Parent { get; private set; }

        /// <summary>
        /// Child (derived) classes
        /// </summary>
        public List<ReflectionClass> Children { get; private set; }

        /// <summary>
        /// Class properties (local to this class only)
        /// Use the AllProperties to retrieve merged list
        /// </summary>
        public List<ReflectionProperty> ClassProperties { get; private set; }

        /// <summary>
        /// Class default values (defined in this class)
        /// </summary>
        public List<ReflectionDefaultValue> ClassDefaultValues { get; private set; }

        /// <summary>
        /// Get all properties defined in this class an the base classes
        /// </summary>
        public List<ReflectionProperty> AllProperties
        {
            get
            {
                var ret = new List<ReflectionProperty>();
                CollectProperties(ref ret);
                return ret;
            }
        }

        /// <summary>
        /// Get all child classes, excluding abstract classes
        /// </summary>
        public List<ReflectionClass> AllChildClassesNoAbstract
        {
            get
            {
                var ret = new List<ReflectionClass>();
                GetChildClasses(false, ref ret);
                return ret;
            }
        }

        /// <summary>
        /// Get all child classes, including abstract classes
        /// </summary>
        public List<ReflectionClass> AllChildClassesWithAbstract
        {
            get
            {
                var ret = new List<ReflectionClass>();
                GetChildClasses(true, ref ret);
                return ret;
            }
        }

        /// <summary>
        /// Initialize class data object
        /// </summary>
        /// <param name="data">Global reflection data this class belongs to</param>
        /// <param name="name">Name of the class type</param>
        internal ReflectionClass(ReflectionData data, string name)
            : base(data, name)
        {
            Children = new List<ReflectionClass>();
            ClassProperties = new List<ReflectionProperty>();
            ClassDefaultValues = new List<ReflectionDefaultValue>();
        }

        /// <summary>
        /// Check if this class derived from specified class
        /// </summary>
        /// <param name="cls">The base class to check if we derive from it</param>
        /// <returns>True if this class derived from specified base class or false if not</returns>
        public bool IsA(ReflectionClass cls)
        {
            if (cls == this)
                return true;

            if (Parent != null)
                return Parent.IsA(cls);

            return false;
        }

        /// <summary>
        /// Collect (recursively) properties into a list
        /// </summary>
        /// <param name="list">The list of properties to collect them into</param>
        private void CollectProperties(ref List<ReflectionProperty> list)
        {
            if ( Parent != null )
                Parent.CollectProperties(ref list);

            foreach (var prop in ClassProperties)
                list.Add(prop);
        }

        /// <summary>
        /// Collect (recursively) classes into a list
        /// </summary>
        /// <param name="includeAbstract"></param>
        /// <param name="retClasses"></param>
        private void GetChildClasses( bool includeAbstract, ref List<ReflectionClass> retClasses )
        {
            if (includeAbstract || !IsAbstract)
                retClasses.Add( this );

            foreach ( var childClass in Children )
                childClass.GetChildClasses( includeAbstract, ref retClasses );
        }

        /// <summary>
        /// Find a default value override for given property name
        /// </summary>
        /// <param name="propertyName">Name of the property for which we want to lookup the default value</param>
        /// <returns>Default value or a null string if there's no value definition</returns>
        public string FindDefaultValue(string propertyName)
        {
            // search local overrides
            foreach (var defaultVar in ClassDefaultValues)
                if (defaultVar.Name == propertyName)
                    return defaultVar.Value;

            // try in base class
            if (Parent != null)
                return Parent.FindDefaultValue(propertyName);

            // nothing found
            return null;
        }

        /// <summary>
        /// XML loading bullshit - load a class definition from XML
        /// </summary>
        /// <param name="data">Reflection data we are loading the class into</param>
        /// <param name="log">General error/progress reporting interface</param>
        /// <param name="node">Source XML node</param>
        /// <returns></returns>
        public static bool LoadFromNode(ReflectionData data, IGenericFileErrorReporter log, XmlNode node)
        {
            // name attribute is required
            string className = Raw.Utils.Attr(node, "name");
            if (className == "")
            {
                log.Error("Missing attribute 'name' for class");
                return false;
            }

            // get the class definition object
            // NOTE: this may return NULL on symbol name conflicts
            var classDef = data.GetClassDef(className);
            if (classDef == null)
            {
                log.Error(String.Format("Class {0} cannot be registered because the name is already used for something else", className));
                return false;
            }

            // we are trying to define the symbol twice
            if ( classDef.IsDefined )
            {
                log.Error(String.Format("Class {0} already defined", className));
                return false;
            }

            // determine base class name
            var baseClassName = Raw.Utils.Attr(node, "base");
            if (baseClassName != "")
            {
                var baseClassDef = data.GetClassDef(baseClassName);
                if (baseClassDef == null)
                {
                    log.Error(String.Format("Class {0} defines a base class name {1} that could not be resolved", className, baseClassName));
                    return false;
                }

                // set parent class
                classDef.Parent = baseClassDef;
                baseClassDef.Children.Add(classDef);
            }

            // set additional properties
            classDef.IsAbstract = Raw.Utils.Attr(node, "abstract", false);
            classDef.Comment = Raw.Utils.Attr(node, "comment");
            classDef.ClassKeyName = Raw.Utils.Attr(node, "key");

            // load props
            int numPropLoadingErrors = 0;
            foreach (XmlNode childNode in node.ChildNodes)
            {
                if (childNode.Name == "property")
                {
                    if (!ReflectionProperty.LoadFromNode(classDef, log, childNode))
                        numPropLoadingErrors += 1;
                }

                else if (childNode.Name == "default")
                {
                    if (!ReflectionDefaultValue.LoadFromNode(classDef, log, childNode))
                        numPropLoadingErrors += 1;
                }
            }

            // well, there were errors loading class properties
            if (numPropLoadingErrors > 0)
            {
                log.Error(String.Format("Found {0} error(s) loading properties for class {1}. Possible corruption of XMLs.", numPropLoadingErrors, className ));
                return false;
            }

            // no loading errors
            return true;
        }
    }   
}
